/*!
    \copyright  Copyright (c) 2023 - 2025 Qualcomm Technologies International, Ltd.
                All Rights Reserved.
                Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \file
    \ingroup    handset_service
    \brief      The Persistent Device Data User (PDDU) implementation for the Handset Service
*/
#include "handset_service_pddu.h"
#include "handset_service_config.h"
#include "handset_service.h"

#include <bt_device.h>
#include <csrtypes.h>
#include <device.h>
#include <device_db_serialiser.h>
#include <device_properties.h>
#include <logging.h>
#include <pddu_map.h>

static void handsetServicePddu_DeserialisePddAndIncrementIndexU8(uint8 *data, uint8 *buf, uint16 index)
{
    *data = buf[index];
}

static void handsetServicePddu_DeserialisePddAndIncrementIndexU16(uint16 *data, uint8 *buf, uint16 index)
{
    *data = buf[index + 1] << 8 | buf[index];
}

static void handsetServicePddu_DeserialisePddAndIncrementIndexU32(uint32 *data, uint8 *buf, uint16 index)
{
    *data = buf[index + 3] << 24 | buf[index + 2] << 16 | buf[index + 1] << 8 | buf[index];
}

static pdd_size_t handsetServicePddu_GetDeviceDataLen(device_t device)
{
    uint8 size_needed = 0;
    handset_service_config_t *handset_service_data = NULL;
    handset_service_config_v1_t *handset_service_data_v1 = NULL;
    size_t handset_service_size = 0;

    if (device != NULL)
    {
        bool table_exists = FALSE;

        table_exists = Device_GetProperty(device, device_property_handset_service_config,
                                          (void *)&handset_service_data, &handset_service_size) ||
                       Device_GetProperty(device, device_property_v1_handset_service_config,
                                          (void *)&handset_service_data_v1, &handset_service_size);

        /* Regardless of which property version exists, we always want to write the latest version,
         * i.e. handset_service_config_t. Any old existing properties will be converted soon to the latest version. */
        if (table_exists)
        {
            size_needed = sizeof(handset_service_config_t);
        }
        else if (device == BtDevice_GetSelfDevice())
        {
            /* The property has never been written -- always fill in a valid default */
            size_needed = sizeof(handset_service_config_t);
        }

        DEBUG_LOG_INFO("handsetServicePddu_GetDeviceDataLen exists=%d,size=%d",
                       table_exists, size_needed);
    }
    return size_needed;
}

static void handsetServicePddu_SerialisePersistentDeviceData(device_t device, void *buf, pdd_size_t offset)
{
    UNUSED(offset);

    handset_service_config_t *handset_service_config = NULL;
    handset_service_config_v1_t *handset_service_config_v1 = NULL;
    size_t handset_service_config_size = 0;
    uint8 * buffer = buf;

    if (Device_GetProperty(device, device_property_handset_service_config, (void *)&handset_service_config, &handset_service_config_size))
    {
        DEBUG_LOG_INFO("handsetServicePddu_SerialisePersistentDeviceData serialise table size=%d", handset_service_config_size);

        /* store the config data to the caller's buffer */
        memcpy(buffer, handset_service_config, handset_service_config_size);
    }
    else if (Device_GetProperty(device, device_property_v1_handset_service_config, (void *)&handset_service_config_v1, &handset_service_config_size))
    {
        DEBUG_LOG_INFO("handsetServicePddu_SerialisePersistentDeviceData (v1) serialise table size=%d", handset_service_config_size);

        /* Convert the old property to the format of the current/latest one */
        handset_service_config_t pdd = { 0 };

        pdd.acl_connect_attempt_limit = handset_service_config_v1->acl_connect_attempt_limit;
        pdd.enable_connection_barge_in = handset_service_config_v1->enable_connection_barge_in;
        pdd.enable_unlimited_acl_reconnection = handset_service_config_v1->enable_unlimited_acl_reconnection;
        pdd.enable_consolidated_handset_count = HANDSET_DEFAULT_ENABLE_CONSOLIDATED_HANDSET_COUNT;
        pdd.max_bredr_connections = handset_service_config_v1->max_bredr_connections;
        pdd.unlimited_reconnection_page_interval_ms = HandsetService_CalculatePageIntervalMsValue(handset_service_config_v1->page_interval);
        pdd.initial_page_timeout_slots = handset_service_config_v1->page_timeout;
        pdd.initial_page_interval_ms = handsetService_BredrAclConnectRetryDelayMs();

        /* store the config data to the caller's buffer */
        memcpy(buffer, &pdd, sizeof(handset_service_config_t));
    }
    else if (device == BtDevice_GetSelfDevice())
    {
        DEBUG_LOG_INFO("handsetServicePddu_SerialisePersistentDeviceData default table size=%d", sizeof(handset_service_config_t));

        HandsetService_SetDefaultConfig(buffer, sizeof(handset_service_config_t));
    }
}

static void handsetServicePddu_DeserialisePersistentDeviceData(device_t device, void *buf, pdd_size_t data_length, pdd_size_t offset)
{
    UNUSED(offset);

    uint8 * buffer = buf;

    DEBUG_LOG_VERBOSE("handsetServicePddu_DeserialisePersistentDeviceData data_length=%d", data_length);

    Device_SetProperty(device, device_property_handset_service_config, buffer, data_length);
}

void HandsetServicePddu_RegisterPdduInternal(void)
{
    DeviceDbSerialiser_RegisterPersistentDeviceDataUser(
        PDDU_ID_HANDSET_SERVICE,
        handsetServicePddu_GetDeviceDataLen,
        handsetServicePddu_SerialisePersistentDeviceData,
        handsetServicePddu_DeserialisePersistentDeviceData);
}

void HandsetServicePddu_NormalizeHandsetServiceConfig(void)
{
    device_t device = BtDevice_GetSelfDevice();
    if (device)
    {
        void *config = NULL;
        uint16 index = 0;
        handset_service_config_t new_config = {0};
        size_t size = 0;
        bool updated = FALSE;

        if (Device_GetProperty(device, device_property_handset_service_config, (void **) &config, &size))
        {
            updated = TRUE;
            index = offsetof(handset_service_config_t, max_bredr_connections);
            if (index + sizeof(uint8_t) <= size)
            {
                handsetServicePddu_DeserialisePddAndIncrementIndexU8(&new_config.max_bredr_connections, config, index);
            }
            else
            {
#ifdef ENABLE_MULTIPOINT
                new_config.max_bredr_connections = 2;
#else
                new_config.max_bredr_connections = 1;
#endif
            }

            index = offsetof(handset_service_config_t, acl_connect_attempt_limit);
            if (index + sizeof(uint8_t) <= size)
            {
                handsetServicePddu_DeserialisePddAndIncrementIndexU8(&new_config.acl_connect_attempt_limit, config, index);
            }
            else
            {
#ifdef ENABLE_MULTIPOINT
                new_config.acl_connect_attempt_limit = 2;
#else
                new_config.acl_connect_attempt_limit = 3;
#endif
            }

            index = offsetof(handset_service_config_t, max_le_connections);
            if (index + sizeof(uint8_t) <= size)
            {
                handsetServicePddu_DeserialisePddAndIncrementIndexU8(&new_config.max_le_connections, config, index);
            }
            else
            {
#ifdef ENABLE_LE_MULTIPOINT
                new_config.max_le_connections = 2;
#else
                new_config.max_le_connections = 1;
#endif
            }
            /* Increment the index by uint8_t here, so the next configuration can be read from the subsequent index */
            index += sizeof(uint8_t);
            if (index + sizeof(uint8_t) <= size)
            {
                uint8_t buffer = ((uint8_t *)config)[index];
                new_config.enable_connection_barge_in = (buffer >> 0) & 0x01;
                new_config.enable_unlimited_acl_reconnection = (buffer >> 1) & 0x01;
                new_config.enable_consolidated_handset_count = (buffer >> 2) & 0x01;
                new_config.unused = (buffer >> 3) & 0x07;
                new_config.version_minor = (buffer >> 6) & 0x03;
            }
            else
            {
#ifdef ENABLE_CONNECTION_BARGE_IN
                new_config.enable_connection_barge_in = TRUE,
#else
                new_config.enable_connection_barge_in = FALSE,
#endif
                new_config.enable_unlimited_acl_reconnection = FALSE;
                new_config.enable_consolidated_handset_count = HANDSET_DEFAULT_ENABLE_CONSOLIDATED_HANDSET_COUNT;
                new_config.unused = 0;
                new_config.version_minor = 1;
            }

            index = offsetof(handset_service_config_t, unlimited_reconnection_page_interval_ms);
            if (index + sizeof(uint32_t) <= size)
            {
               uint32 temp_value = 0;
               handsetServicePddu_DeserialisePddAndIncrementIndexU32(&temp_value, config, index);
               new_config.unlimited_reconnection_page_interval_ms = temp_value;
            }
            else
            {
                new_config.unlimited_reconnection_page_interval_ms = 30 * MS_PER_SEC;
            }

            index = offsetof(handset_service_config_t, initial_page_timeout_slots);
            if (index + sizeof(uint16_t) <= size)
            {
                uint16 temp_value = 0;
                handsetServicePddu_DeserialisePddAndIncrementIndexU16(&temp_value, config, index);
                new_config.initial_page_timeout_slots = temp_value;
            }
            else
            {
#ifdef ENABLE_MULTIPOINT
                new_config.initial_page_timeout_slots = MS_TO_BT_SLOTS(PAGE_TIMEOUT_IN_MS);
#else
                new_config.initial_page_timeout_slots = MS_TO_BT_SLOTS(10 * MS_PER_SEC);
#endif
            }

            index = offsetof(handset_service_config_t, initial_page_interval_ms);
            if (index + sizeof(uint16_t) <= size)
            {
                uint16 temp_value = 0;
                handsetServicePddu_DeserialisePddAndIncrementIndexU16(&temp_value, config, index);
                new_config.initial_page_interval_ms = temp_value;
            }
            else
            {
                new_config.initial_page_interval_ms = handsetService_BredrAclConnectRetryDelayMs();
            }

            index = offsetof(handset_service_config_t, unlimited_reconnection_page_timeout_slots);
            if (index + sizeof(uint16_t) <= size)
            {
                uint16 temp_value = 0;
                handsetServicePddu_DeserialisePddAndIncrementIndexU16(&temp_value, config, index);
                new_config.unlimited_reconnection_page_timeout_slots = temp_value;
            }
            else
            {
#ifdef ENABLE_MULTIPOINT
                new_config.unlimited_reconnection_page_timeout_slots = MS_TO_BT_SLOTS(PAGE_TIMEOUT_IN_MS);
#else
                new_config.unlimited_reconnection_page_timeout_slots = MS_TO_BT_SLOTS(10 * MS_PER_SEC);
#endif
            }

            index = offsetof(handset_service_config_t, abort_on_failed_pairing);
            if (index + sizeof(uint8_t) <= size)
            {
                uint8 temp_value = 0;
                handsetServicePddu_DeserialisePddAndIncrementIndexU8(&temp_value, config, index);
                new_config.abort_on_failed_pairing = temp_value;
            }
            else
            {
                new_config.abort_on_failed_pairing = TRUE;
            }

            index = offsetof(handset_service_config_t, pairing_timeout_user_s);
            if (index + sizeof(uint32_t) <= size)
            {
                uint32 temp_value = 0;
                handsetServicePddu_DeserialisePddAndIncrementIndexU32(&temp_value, config, index);
                new_config.pairing_timeout_user_s = temp_value;
            }
            else
            {
                new_config.pairing_timeout_user_s = 120;
            }

            index = offsetof(handset_service_config_t, pairing_timeout_automatic_s);
            if (index + sizeof(uint32_t) <= size)
            {
                uint32 temp_value = 0;
                handsetServicePddu_DeserialisePddAndIncrementIndexU32(&temp_value, config, index);
                new_config.pairing_timeout_automatic_s = temp_value;
            }
            else
            {
                new_config.pairing_timeout_automatic_s = 300;
            }

            /* Check if the version_minor is 0 then upate to 1 */
            switch (new_config.version_minor)
            {
                case 0:
                {
                    new_config.enable_consolidated_handset_count = HANDSET_DEFAULT_ENABLE_CONSOLIDATED_HANDSET_COUNT;
                    new_config.version_minor = 1;

                    updated = TRUE;
                }
                break;

                case 1:
                    /* Version is correct */
                break;

                default:
                {
                    DEBUG_LOG_PANIC("HandsetServicePddu_NormalizeHandsetServiceConfig: unsupported version %u", new_config.version_minor);
                }
                break;
            }
        }
        else
        {
            /* Only create the Handset Serivce PDD if it doesn't exist when deserialising the device data, and the
                       BT Device PDD version does exist, if neither exist, this represents a factory reset, do nothing. */
            handset_service_config_v1_t *v1_config = NULL;
            if (Device_GetProperty(device, device_property_v1_handset_service_config, (void **) &v1_config, &size))
            {
                PanicFalse(size == sizeof(handset_service_config_v1_t));

                new_config.acl_connect_attempt_limit = v1_config->acl_connect_attempt_limit;
                new_config.enable_connection_barge_in = v1_config->enable_connection_barge_in;
                new_config.enable_unlimited_acl_reconnection = v1_config->enable_unlimited_acl_reconnection;
                new_config.enable_consolidated_handset_count = HANDSET_DEFAULT_ENABLE_CONSOLIDATED_HANDSET_COUNT;
                new_config.max_bredr_connections = v1_config->max_bredr_connections;
                new_config.unlimited_reconnection_page_interval_ms = HandsetService_CalculatePageIntervalMsValue(v1_config->page_interval);
                new_config.unlimited_reconnection_page_timeout_slots = v1_config->page_timeout;
                new_config.initial_page_interval_ms = handsetService_BredrAclConnectRetryDelayMs();
                new_config.initial_page_timeout_slots = v1_config->page_timeout;
                new_config.max_le_connections = v1_config->max_le_connections;
                new_config.version_minor = 1;
                new_config.abort_on_failed_pairing = TRUE;
                new_config.pairing_timeout_user_s = 120;
                new_config.pairing_timeout_automatic_s = 300;

                updated = TRUE;
            }
        }

        if (updated)
        {
            DEBUG_LOG("HandsetServicePddu_NormalizeHandsetServiceConfig: updating to size %u version %u", sizeof new_config, new_config.version_minor);
            PanicFalse(Device_SetProperty(device, device_property_handset_service_config, (void *)&new_config, sizeof new_config));
        }
    }
}
